import java.util.*;
import java.io.*;
public class Main
{	
	private static BufferedWriter filePrinter; 
	public static void main(String[] args)throws Exception
	{
		long _begin = System.currentTimeMillis();		
		Data.readData();
		//System.out.println("Normal Garbage");
		//Data.printNormalGarbageMatrix();
		//System.out.println("Biological Risk Garbage");
		//Data.printBiologicalRiskGarbageMatrix();
		
		System.out.println("Biological Garbage Problem");
		Data.setMatrix(Data.getBiologicalRiskGarbageMatrix());
		
		
		//System.out.println(Data.getRandomChromosome());
		//Individual i = new Individual();
		//System.out.println(i);		
		//System.out.println(i.getFitnessDebug());		
		
		//solveProblem();
		filePrinter = new BufferedWriter(new FileWriter("experiment.txt"));
		doExperiments();
		filePrinter.close();
		
		
		//System.out.println("Normal Garbage Problem");
		//Data.setMatrix(Data.getNormalGarbageMatrix());
		//solveProblem();

		System.out.println((System.currentTimeMillis()-_begin)+" miliseconds of excecution");
	}
	private static void doExperiments()throws Exception
	{
		int id = 1;
		for (int population = 100; population <= 500; population+=100)
		{
			for (int iterations = 10; iterations <= 100; iterations += 10)
				runExperiment(id++,population,iterations);
			runExperiment(id++,population,200);
		}		
	}
	private static void runExperiment( final int experimentID ,final int population , final int iterations )throws Exception
	{
		
		filePrinter.write(experimentID+"\t"+population+"\t"+iterations+"\t");
		Data.setPopulation(population);
		Data.setIterations(iterations);
		Individual best = solveProblem(experimentID+"");
		filePrinter.write(Util.format(best.getFitness(), 15)+"\t"+best.getCost()+"\t");
		double slack[] = best.getFenotipe().getSlack();
		double totalSlack = 0;
		for (int i = 0; i < slack.length; i++)
		{
			filePrinter.write(Util.format(slack[i],1)+"\t");
			totalSlack += slack[i];
		}
		filePrinter.write(Util.format(totalSlack,1)+"\t");
		
		int[][] trucks = best.getTrucks();
		for (int day = 0; day < trucks.length; day++)
			for (int truckType = 0; truckType < trucks[day].length; truckType++)
				filePrinter.write(trucks[day][truckType]+"\t");
				
		filePrinter.write("\n");
	}
	private static void testSelector(final ArrayList<Individual> population)
	{
		double totalFitness = 0;
		for (Individual i : population) 
			totalFitness += i.getFitness();
		double totalP = 0;
		for (int i = 0; i < population.size(); i++)
		{
			double f = population.get( i ).getFitness();
			double p = f/totalFitness;
			totalP += p;
			System.out.println(i+" ->" +Util.format( f ,20 ) +" prob = "+Util.format( p ,20 ));
		}
		System.out.println("Total p "+totalP);
		
		int selectionCount[] = new int[population.size()];
		final int TOTAL_SELECTION = 10000000;
		for (int selection = 0; selection < TOTAL_SELECTION; selection++)
		{
			double fitnessAccum = 0;
			double random = Random.getRandomPositiveReal(totalFitness);
			for (int i = 0; i < population.size(); i++)
			{
				fitnessAccum += population.get(i).getFitness();
				if( fitnessAccum >= random )
				{
					selectionCount[i]++;
					break;
				}
			}	
		}
		System.out.println("results");
		for (int i = 0; i < population.size(); i++)
		{
			double f = population.get( i ).getFitness();
			double p = f/totalFitness;
			double prop = selectionCount[i]/(double)TOTAL_SELECTION; 
			System.out.println(i+" ->" +Util.format( f ,20 ) +" prob = "+Util.format( p ,20 ) + " selections: "+selectionCount[i]+" prop : "+Util.format(prop,20));
		}
	}
	private static Individual solveProblem( final String experimentId )
	{
		//Initialization
		ArrayList<Individual> population = new ArrayList<Individual>(Data.POPULATION_COUNT);
		for (int i = 0; i < Data.POPULATION_COUNT; i++)
			population.add( new Individual());

		Collections.sort(population);
		
		//for (int i = 0; i < population.size(); i++)
		//{		
			//System.out.println("Individual "+i+" :: >> "+Util.format(population.get(i).getFitness(),20));
			//System.out.println(population.get(i));
		//}
		
		Individual bestAtFirst = population.get(0).clone();
		//testSelector(population);
		
		for (int iter = 0; iter < Data.TOTAL_ITERATIONS; iter++)
		{
			System.out.print("Experiment: "+experimentId+" , Iteration "+iter+" ->");
			Collections.sort(population);
			ArrayList<Individual> nextGeneration = getNextGeneration( population );							
			population = nextGeneration;
		}	
		Collections.sort(population);
		//for (int i = 0; i < population.size(); i++)System.out.println(i+" ->" +Util.format( population.get( i ).getFitness(),20 ) );
		
		System.out.println("($"+Util.format(bestAtFirst.getCost(),2)+")FirstBest  ->" + bestAtFirst );
		System.out.println("($"+Util.format(population.get( 0 ).getCost(),2)+")Best       ->" +population.get( 0 ) );
		System.out.println("($"+Util.format(population.get( population.size()-1 ).getCost(),2)+")Worst      ->" +population.get( population.size()-1 ) );
		System.out.println("bestFitness->"+Util.format( population.get(0).getFitnessDebug(),20));
		
		return population.get(0);
	}
	private static ArrayList<Individual> getNextGeneration( final ArrayList<Individual> population)
	{
		ArrayList<Individual> tmp = new ArrayList<Individual>();
		
		double totalFitness = 0;
		for (Individual i : population) 
			totalFitness += i.getFitness();
		
		System.out.println("Total Fitness "+Util.format( totalFitness , 15 )+" bestFitness: "+Util.format( population.get(0).getFitness() ,15) );
		
		for (int i = 0; i < Data.POPULATION_COUNT/2; i++)
		{
			Individual father = selectIndividual( population , totalFitness );
			Individual mother = selectIndividual( population , totalFitness );
			father.mutate();
			mother.mutate();
			ArrayList<Individual> offspring = Individual.crossOver( mother ,  father );
			tmp.addAll(offspring);
		}			
		tmp.addAll(population);
		Collections.sort(tmp);
		
		//for (int i = 0; i < tmp.size(); i++)System.out.println(i+" .... "+Util.format( tmp.get(i).getFitness() , 20));
		
		ArrayList<Individual> ans = new ArrayList<Individual>();
		for (int i = 0; i < Data.POPULATION_COUNT; i++)
			ans.add( tmp.get(i));
		return ans;
	}
	private static Individual selectIndividual(final ArrayList<Individual> population, final double totalFitness)
	{
		double fitnessAccum = 0;
		double random = Random.getRandomPositiveReal(totalFitness);
		for (int i = 0; i < population.size(); i++)
		{
			fitnessAccum += population.get(i).getFitness();
			if( fitnessAccum >= random )
			{
				return population.get(i).clone();
			}
		}	
		return population.get(0);
	}
}
